 /*******************************************************************************
  * Copyright (c) 2003, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.internal.progress;

 import java.util.ArrayList ;
 import java.util.Arrays ;
 import java.util.Comparator ;
 import java.util.Date ;
 import java.util.HashMap ;
 import java.util.HashSet ;

 import org.eclipse.core.commands.common.EventManager;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.ui.actions.ActionFactory;
 import org.eclipse.ui.progress.IProgressConstants;

 /**
  * This singleton remembers all JobTreeElements that should be preserved (e.g.
  * because their associated Jobs have the "keep" property set).
  */
 public class FinishedJobs extends EventManager {

     /*
      * Interface for notify listeners.
      */
     static interface KeptJobsListener {

         /**
          * A job to be kept has finished
          *
          * @param jte
          */
         void finished(JobTreeElement jte);

         /**
          * A kept job has been removed.
          *
          * @param jte
          */
         void removed(JobTreeElement jte);
     }

     private static FinishedJobs theInstance;

     private IJobProgressManagerListener listener;

     private HashSet keptjobinfos = new HashSet ();

     private HashMap finishedTime = new HashMap ();

     private static JobTreeElement[] EMPTY_INFOS;

     public static synchronized FinishedJobs getInstance() {
         if (theInstance == null) {
             theInstance = new FinishedJobs();
             EMPTY_INFOS = new JobTreeElement[0];
         }
         return theInstance;
     }

     private FinishedJobs() {
         listener = new IJobProgressManagerListener() {
             public void addJob(JobInfo info) {
                 checkForDuplicates(info);
             }

             public void addGroup(GroupInfo info) {
                 checkForDuplicates(info);
             }

             public void refreshJobInfo(JobInfo info) {
                 checkTasks(info);
             }

             public void refreshGroup(GroupInfo info) {
             }

             public void refreshAll() {
             }

             public void removeJob(JobInfo info) {
                 if (keep(info)) {
                     checkForDuplicates(info);
                     add(info);
                 }
             }

             public void removeGroup(GroupInfo group) {
             }

             public boolean showsDebug() {
                 return false;
             }
         };
         ProgressManager.getInstance().addListener(listener);
     }

     /**
      * Returns true if JobInfo indicates that it must be kept.
      */
     static boolean keep(JobInfo info) {
         Job job = info.getJob();
         if (job != null) {
             Object prop = job.getProperty(ProgressManagerUtil.KEEP_PROPERTY);
             if (prop instanceof Boolean ) {
                 if (((Boolean ) prop).booleanValue()) {
                     return true;
                 }
             }

             prop = job.getProperty(ProgressManagerUtil.KEEPONE_PROPERTY);
             if (prop instanceof Boolean ) {
                 if (((Boolean ) prop).booleanValue()) {
                     return true;
                 }
             }

             IStatus status = job.getResult();
             if (status != null && status.getSeverity() == IStatus.ERROR) {
                 return true;
             }
         }
         return false;
     }

     /**
      * Register for notification.
      */
     void addListener(KeptJobsListener l) {
         addListenerObject(l);
     }

     /**
      * Deregister for notification.
      */
     void removeListener(KeptJobsListener l) {
         removeListenerObject(l);
     }

     private void checkForDuplicates(GroupInfo info) {
         Object [] objects = info.getChildren();
         for (int i = 0; i < objects.length; i++) {
             if (objects[i] instanceof JobInfo) {
                 checkForDuplicates((JobInfo) objects[i]);
             }
         }
     }

     private void checkForDuplicates(JobTreeElement info) {
         JobTreeElement[] toBeRemoved = findJobsToRemove(info);
         if (toBeRemoved != null) {
             for (int i = 0; i < toBeRemoved.length; i++) {
                 remove(toBeRemoved[i]);
             }
         }
     }

     /**
      * Add given Job to list of kept jobs.
      */
     private void add(JobInfo info) {
         boolean fire = false;

         synchronized (keptjobinfos) {
             if (!keptjobinfos.contains(info)) {
                 keptjobinfos.add(info);

                 long now = System.currentTimeMillis();
                 finishedTime.put(info, new Long (now));

                 Object parent = info.getParent();
                 if (parent != null && !keptjobinfos.contains(parent)) {
                     keptjobinfos.add(parent);
                     finishedTime.put(parent, new Long (now));
                 }

                 fire = true;
             }
         }

         if (fire) {
             Object l[] = getListeners();
             for (int i = 0; i < l.length; i++) {
                 KeptJobsListener jv = (KeptJobsListener) l[i];
                 jv.finished(info);
             }
         }
     }

     static void disposeAction(JobTreeElement jte) {
         if (jte.isJobInfo()) {
             JobInfo ji = (JobInfo) jte;
             Job job = ji.getJob();
             if (job != null) {
                 Object prop = job
                         .getProperty(IProgressConstants.ACTION_PROPERTY);
                 if (prop instanceof ActionFactory.IWorkbenchAction) {
                     ((ActionFactory.IWorkbenchAction) prop).dispose();
                 }
             }
         }
     }

     private JobTreeElement[] findJobsToRemove(JobTreeElement info) {

         if (info.isJobInfo()) {
             Job myJob = ((JobInfo) info).getJob();

             if (myJob != null) {

                 Object prop = myJob
                         .getProperty(ProgressManagerUtil.KEEPONE_PROPERTY);
                 if (prop instanceof Boolean && ((Boolean ) prop).booleanValue()) {
                     ArrayList found = null;
                     JobTreeElement[] all;
                     synchronized (keptjobinfos) {
                         all = (JobTreeElement[]) keptjobinfos
                                 .toArray(new JobTreeElement[keptjobinfos.size()]);
                     }
                     for (int i = 0; i < all.length; i++) {
                         JobTreeElement jte = all[i];
                         if (jte != info && jte.isJobInfo()) {
                             Job job = ((JobInfo) jte).getJob();
                             if (job != null && job != myJob
                                     && job.belongsTo(myJob)) {
                                 if (found == null) {
                                     found = new ArrayList ();
                                 }
                                 found.add(jte);
                             }
                         }
                     }
                     if (found != null) {
                         return (JobTreeElement[]) found
                                 .toArray(new JobTreeElement[found.size()]);
                     }
                 }
             }
         }
         return null;
     }

     private void checkTasks(JobInfo info) {
         if (keep(info)) {
             TaskInfo tinfo = info.getTaskInfo();
             if (tinfo != null) {
                 JobTreeElement[] toBeRemoved = null;
                 boolean fire = false;
                 JobTreeElement element = (JobTreeElement) tinfo.getParent();
                 synchronized (keptjobinfos) {
                     if (element == info && !keptjobinfos.contains(tinfo)) {
                         toBeRemoved = findJobsToRemove(element);
                         keptjobinfos.add(tinfo);
                         finishedTime.put(tinfo, new Long (System
                                 .currentTimeMillis()));
                     }
                 }

                 if (toBeRemoved != null) {
                     for (int i = 0; i < toBeRemoved.length; i++) {
                         remove(toBeRemoved[i]);
                     }
                 }

                 if (fire) {
                     Object l[] = getListeners();
                     for (int i = 0; i < l.length; i++) {
                         KeptJobsListener jv = (KeptJobsListener) l[i];
                         jv.finished(info);
                     }
                 }
             }
         }
     }
     
     public void removeErrorJobs() {
         JobTreeElement[] infos = getJobInfos();
         for (int i = 0; i < infos.length; i++) {
             if (infos[i].isJobInfo()) {
                 JobInfo info1 = (JobInfo) infos[i];
                 Job job = info1.getJob();
                 if (job != null) {
                     IStatus status = job.getResult();
                     if (status != null && status.getSeverity() == IStatus.ERROR) {
                         JobTreeElement topElement = (JobTreeElement) info1
                                 .getParent();
                         if (topElement == null) {
                             topElement = info1;
                         }
                         FinishedJobs.getInstance().remove(topElement);
                     }
                 }
             }
         }
     }

     boolean remove(JobTreeElement jte) {
         boolean fire = false;
         boolean removed = false;

         synchronized (keptjobinfos) {
             if (keptjobinfos.remove(jte)) {
                 removed = true;
                 finishedTime.remove(jte);
                 disposeAction(jte);

                 // delete all elements that have jte as their direct or indirect
 // parent
 JobTreeElement jtes[] = (JobTreeElement[]) keptjobinfos
                         .toArray(new JobTreeElement[keptjobinfos.size()]);
                 for (int i = 0; i < jtes.length; i++) {
                     JobTreeElement parent = (JobTreeElement) jtes[i]
                             .getParent();
                     if (parent != null) {
                         if (parent == jte || parent.getParent() == jte) {
                             if (keptjobinfos.remove(jtes[i])) {
                                 disposeAction(jtes[i]);
                             }
                             finishedTime.remove(jtes[i]);
                         }
                     }
                 }
                 fire = true;
             }
         }

         if (fire) {
             // notify listeners
 Object l[] = getListeners();
             for (int i = 0; i < l.length; i++) {
                 KeptJobsListener jv = (KeptJobsListener) l[i];
                 jv.removed(jte);
             }
         }
         return removed;
     }

     /**
      * Returns all kept elements sorted by finished date.
      */
     JobTreeElement[] getJobInfos() {
         JobTreeElement[] all;
         if (keptjobinfos.isEmpty()) {
             return EMPTY_INFOS;
         }

         synchronized (keptjobinfos) {
             all = (JobTreeElement[]) keptjobinfos
                     .toArray(new JobTreeElement[keptjobinfos.size()]);
         }
         Arrays.sort(all, new Comparator () {
             public int compare(Object o1, Object o2) {
                 long t1 = getFinishedDateAsLong((JobTreeElement) o1);
                 long t2 = getFinishedDateAsLong((JobTreeElement) o2);
                 if (t1 < t2) {
                     return -1;
                 }
                 if (t1 > t2) {
                     return 1;
                 }
                 return 0;
             }
         });
         return all;
     }

     private long getFinishedDateAsLong(JobTreeElement jte) {
         Object o = finishedTime.get(jte);
         if (o instanceof Long ) {
             return ((Long ) o).longValue();
         }
         return 0;
     }

     /**
      * Get the date that indicates the finish time.
      *
      * @param jte
      * @return Date
      */
     public Date getFinishDate(JobTreeElement jte) {
         Object o = finishedTime.get(jte);
         if (o instanceof Long ) {
             return new Date (((Long ) o).longValue());
         }
         return null;
     }

     /**
      * Return whether or not the kept infos have the element.
      *
      * @param element
      * @return boolean
      */
     public boolean isFinished(JobTreeElement element) {
         return keptjobinfos.contains(element);
     }

     /**
      * Clear all kept jobs.
      */
     public void clearAll() {
         synchronized (keptjobinfos) {
             JobTreeElement[] all = (JobTreeElement[]) keptjobinfos
                     .toArray(new JobTreeElement[keptjobinfos.size()]);
             for (int i = 0; i < all.length; i++) {
                 disposeAction(all[i]);
             }
             keptjobinfos.clear();
             finishedTime.clear();
         }

         // notify listeners
 Object l[] = getListeners();
         for (int i = 0; i < l.length; i++) {
             KeptJobsListener jv = (KeptJobsListener) l[i];
             jv.removed(null);
         }
     }
 }

